﻿#region COPYRIGHT
//--------------------------------------------------------------------------------
// <copyright file="PerPixelNarrowPhase.cs" company="starLiGHT Entertainment Studios">
//    Copyright (c) 2010, 2011
//       Roland Rosenkranz (Glatzemann@email.de)
// </copyright>
// <license>
//   This file is part of starLiGHT.Collision.
//
//   starLiGHT.Collision is free software: you can redistribute it and/or modify
//   it under the terms of the GNU Lesser General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   starLiGHT.Collision is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU Lesser General Public License for more details.
//
//   You should have received a copy of the GNU Lesser General Public License
//   along with starLiGHT.Collision. If not, see http://www.gnu.org/licenses/.
//    
//   ADDITIONAL (commercial) LICENSES for starLiGHT.Collision are available on request.
// </license>
// <version>
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev:: 3345                  $:  Revision of last commit *
// * $Author:: unknown            $:  Author of last commit   *
// * $Date:: 2011-01-13 16:01:08 #$:  Date of last commit     *
// ************************************************************
// </version>
//--------------------------------------------------------------------------------
#endregion

namespace starLiGHT.Collision.NarrowPhase
{
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    #endregion

    public class PerPixelNarrowPhase : INarrowPhase
    {
        public int CollisionCount { get; set; }

        public CollisionManifold<ICollidable> ProcessPair(CollisionManager collisionManager, UserPair<ICollidable> pair)
        {
            if (collisionManager == null)
            {
                throw new ArgumentNullException("collisionManager");
            }

            if (pair == null)
            {
                throw new ArgumentNullException("pair");
            }

            if (pair.object0 is ICollidable2D && pair.object1 is ICollidable2D)
            {
                return this.ProcessPair(collisionManager, pair, pair.object0 as ICollidable2D, pair.object1 as ICollidable2D);
            }

            throw new NotImplementedException("PerPixelNarrowPhase only supports 2D");
        }

        public bool CheckCompatibility(ICollidable c)
        {
            try
            {
                if (c is ICollidable2D)
                {
                    return ((ICollidable2D)c).Texture != null;
                }
            }
            catch
            {
            }

            return false;
        }

        private CollisionManifold<ICollidable> ProcessPair(CollisionManager collisionManager, UserPair<ICollidable> pair, ICollidable2D object0, ICollidable2D object1)
        {
            // exception for "non texture objects"
            if (object0.Texture == null && object1.Texture == null)
            {
                throw new NotSupportedException("PerPixel collision is not supported because one of the Collidable2D's of the CollisionPair2D has now texture attached.");
            }

            bool intersect = false;

            if (!object0.Transform.IsTransformed && !object1.Transform.IsTransformed)
            {
                // both collidables are untransformed, no need to do special handling
                intersect = this.IntersectPixels(collisionManager, object0.Texture, object1.Texture, object0.WorldBoundingRectangle, object1.WorldBoundingRectangle);
            }
            else
            {
                Matrix transformA = object0.Transform.TransformationMatrix;
                Matrix transformB = object1.Transform.TransformationMatrix;
                Matrix transformAtoB = transformA * Matrix.Invert(transformB);
                Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAtoB);
                Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAtoB);
                Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAtoB);

                intersect = this.IntersectPixels(collisionManager, object0.Texture, object1.Texture, stepX, stepY, yPosInB);
            }

            pair.object0.Colliding = intersect;
            pair.object1.Colliding = intersect;

            if (intersect)
            {
                CollisionManifold<ICollidable> manifold = new CollisionManifold<ICollidable>();
                manifold.NarrowPhaseCollision = intersect;
                manifold.Object0 = pair.object0;
                manifold.Object1 = pair.object1;

                collisionManager.OnCollision(pair, manifold);

                return manifold;
            }
            else
            {
                collisionManager.OnCollisionResolved(pair);

                return null;
            }
        }

        private bool IntersectPixels(CollisionManager collisionManager, Texture2D texA, Texture2D texB, RectangleF rectA, RectangleF rectB)
        {
            int top = Math.Max(rectA.Top, rectB.Top).ToNearestInt();
            int bottom = Math.Min(rectA.Bottom, rectB.Bottom).ToNearestInt();
            int left = Math.Max(rectA.Left, rectB.Left).ToNearestInt();
            int right = Math.Min(rectA.Right, rectB.Right).ToNearestInt();

            collisionManager.DebugDraw.QueueUserRectangle(new RectangleF(left, top, right - left, bottom - top));

            bool[] dataA;
            bool[] dataB;
            collisionManager.CollisionMaskFromCache(texA, out dataA);
            collisionManager.CollisionMaskFromCache(texB, out dataB);

            for (int y = top; y < bottom; y++)
            {
                for (int x = left; x < right; x++)
                {
                    if (dataA[(x - rectA.Left.ToNearestInt()) + ((y - rectA.Top.ToNearestInt()) * rectA.Width.ToNearestInt())] &&
                        dataB[(x - rectB.Left.ToNearestInt()) + ((y - rectB.Top.ToNearestInt()) * rectB.Width.ToNearestInt())])
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private bool IntersectPixels(CollisionManager collisionManager, Texture2D texA, Texture2D texB, Vector2 stepX, Vector2 stepY, Vector2 yPosInB)
        {
            int widthA = texA.Width;
            int heightA = texA.Height;
            int widthB = texB.Width;
            int heightB = texB.Height;

            bool[] dataA; 
            bool[] dataB;
            collisionManager.CollisionMaskFromCache(texA, out dataA); 
            collisionManager.CollisionMaskFromCache(texB, out dataB);

            int xB, yB;
            Vector2 posInB;
            for (int yA = 0; yA < heightA; yA++)
            {
                posInB = yPosInB;

                for (int xA = 0; xA < widthA; xA++)
                {
                    xB = (int)(posInB.X + 0.5f);
                    yB = (int)(posInB.Y + 0.5f);

                    if (0 <= xB && xB < widthB && 0 <= yB && yB < heightB)
                    {
                        if (dataA[xA + (yA * widthA)] && dataB[xB + (yB * widthB)])
                        {
                            return true;
                        }
                    }

                    posInB += stepX;
                }

                yPosInB += stepY;
            }

            return false;
        }
    }
}
